using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using HandyIRCCore;

namespace HandyIRCChat
{
    // Only one instance of HandyLogic can be created (singleton pattern)
    // todo: if was not called ReleaseInstance exception must be generated
    public sealed class HandyLogic
    {
        #region Declarations

        private Options options;

        private Core ircCore = new Core();

        internal static string LogFileName = "..\\..\\HandyLog\\HandyLog.xml";

        private ConsoleObject consoleObj;

        private List<ChannelObject> channels = new List<ChannelObject>();

        private List<PrivateObject> privates = new List<PrivateObject>();

        #region Delegates and Events

        public delegate void LeaveChannelEventHandler(object sender, string channelName);

        public event LeaveChannelEventHandler LeaveChannelEvent;

        public delegate void JoinChannelEventHandler(object sender, string channelName);

        public event JoinChannelEventHandler JoinChannelEvent;

        public delegate void UserJoinEventHandler(object sender, UserJoinEventArgs e);

        public event UserJoinEventHandler UserJoinChannelEvent;

        public delegate void UserLeaveEventHandler(object sender, UserLeaveEventArgs e);

        public event UserLeaveEventHandler UserLeaveChannelEvent;

        public delegate void UserRenameEventHandler(object sender, UserRenameEventArgs e);

        public event UserRenameEventHandler UserRenameEvent;

        public delegate void PrivateObjectCreatedEventHandler(object sender, string userName);

        public event PrivateObjectCreatedEventHandler PrivateObjectCreatedEvent;

        public delegate void ClosePrivateFormEventHandler(object sender, string userName);

        public event ClosePrivateFormEventHandler ClosePrivateFormEvent;

        public delegate void QueryMessageEventHandler(object sender, HandyIRCMessage msg);

        public event QueryMessageEventHandler QueryMessageEvent;

        public delegate void ChannelMessageEventHandler(object sender, HandyIRCMessage msg);

        public event ChannelMessageEventHandler ChannelMessageEvent;

        #endregion

        #endregion

        #region Singleton pattern management

        private static HandyLogic instance;

        private HandyLogic()
        {
            options = Options.CreateInstance();
            options.Load();
        }

        public static HandyLogic CreateInstance()
        {
            // "lazy initialization"
            if (instance == null)
                instance = new HandyLogic();

            return instance;
        }

        public static void ReleaseInstance()
        {
            if (instance != null)
            {
                instance.Options.Save();

                instance.OnReleaseInstance();
                instance = null;
            }
        }

        #endregion

        #region Connect & Disconnect methods

        public void Connect()
        {
            ircCore.Encoding = Encoding.GetEncoding(options.CharacterSet);

            try
            {
                ircCore.Connect(options.Server, options.Port, options.NickName, 
                    options.Name, options.RealName, options.Password, options.Mode);
            }
            catch (CannotConnectException e)
            {
                MessageBox.Show("Couldn't connect to server\nReason: " + e.Message, "Warning",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            catch (AlreadyConnectedException e)
            {
                MessageBox.Show("Couldn't connect to server\nReason: " + e.Message, "Warning",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            if (ircCore.IsConnected)
            {
                ircCore.JoinEvent += new Core.JoinEventHandler(ircCore_JoinEvent);
                ircCore.PartEvent += new Core.PartEventHandler(ircCore_PartEvent);
                ircCore.KickEvent += new Core.KickEventHandler(ircCore_KickEvent);
                ircCore.QuitEvent += new Core.QuitEventHandler(ircCore_QuitEvent);
                ircCore.RPL_NAMREPLYEvent += new Core.RPL_NAMREPLYEventHandler(ircCore_RPL_NAMREPLYEvent);
                ircCore.NickEvent += new Core.NickEventHandler(ircCore_NickEvent);
                ircCore.PrivmsgEvent += new Core.PrivmsgEventHandler(ircCore_PrivmsgEvent);

                consoleObj = ConsoleObject.CreateInstance(this, ircCore);
            }
        }

        public void Disconnect()
        {
            if (ircCore.IsConnected)
            {
                ircCore.JoinEvent -= new Core.JoinEventHandler(ircCore_JoinEvent);
                ircCore.PartEvent -= new Core.PartEventHandler(ircCore_PartEvent);
                ircCore.KickEvent -= new Core.KickEventHandler(ircCore_KickEvent);
                ircCore.QuitEvent -= new Core.QuitEventHandler(ircCore_QuitEvent);
                ircCore.RPL_NAMREPLYEvent -= new Core.RPL_NAMREPLYEventHandler(ircCore_RPL_NAMREPLYEvent);
                ircCore.NickEvent -= new Core.NickEventHandler(ircCore_NickEvent);
                ircCore.PrivmsgEvent -= new Core.PrivmsgEventHandler(ircCore_PrivmsgEvent);

                foreach (ChannelObject obj in channels)
                {
                    if (LeaveChannelEvent != null)
                        LeaveChannelEvent(this, obj.Channel);
                }
                channels.Clear();

                foreach (PrivateObject obj in privates)
                {
                    if (ClosePrivateFormEvent != null)
                        ClosePrivateFormEvent(this, obj.UserName);
                }
                privates.Clear();

                ircCore.Disconnect();
                ConsoleObject.ReleaseInstance();
            }
        }

        #endregion

        #region JoinChannel & LeaveChannel methods

        public void JoinChannel(string channel)
        {
            if (ircCore.IsConnected && GetChannelObject(channel) == null)
            {
                ircCore.SendMessage("JOIN " + channel);
            }
        }

        public void LeaveChannel(string channel)
        {
            if (ircCore.IsConnected && GetChannelObject(channel) != null)
            {
                ircCore.SendMessage("PART " + channel);
            }
        }

        #endregion

        #region Methods

        public ChannelObject GetChannelObject(string channelName)
        {
            ChannelObject result = null;

            foreach (ChannelObject obj in channels)
            {
                if (obj.Channel == channelName)
                {
                    result = obj;
                    break;
                }
            }

            return result;
        }

        public PrivateObject GetPrivateObject(string userName)
        {
            PrivateObject result = null;

            foreach (PrivateObject obj in privates)
            {
                if (obj.UserName == userName)
                {
                    result = obj;
                    break;
                }
            }

            return result;
        }

        public void CreatePrivateObject(string userName, string firstMessage)
        {
            PrivateObject obj = new PrivateObject(this, ircCore, userName, firstMessage);
            privates.Add(obj);

            if (PrivateObjectCreatedEvent != null)
                PrivateObjectCreatedEvent(this, obj.UserName);
        }

        private void OnReleaseInstance()
        {
            if (ircCore.IsConnected)
                Disconnect();
        }

        private void OnLeave(string channel, string name)
        {
            // is me?
            if (ircCore.UserNick == name)
            {
                ChannelObject obj = GetChannelObject(channel);
                channels.Remove(obj);

                if (LeaveChannelEvent != null)
                    LeaveChannelEvent(this, channel);
            }
        }

        private void ircCore_QuitEvent(object sender, HandyIRCMessage msg)
        {
            if (UserLeaveChannelEvent != null)
                UserLeaveChannelEvent(this, new UserLeaveEventArgs(msg.Nick));

            if (ClosePrivateFormEvent != null)
                ClosePrivateFormEvent(this, msg.Nick);

            PrivateObject obj = GetPrivateObject(msg.Nick);
            if (obj != null)
                privates.Remove(obj);
        }

        private void ircCore_KickEvent(object sender, HandyIRCMessage msg)
        {
            OnLeave(msg.Parameters[0], msg.Parameters[1]);

            if (UserLeaveChannelEvent != null)
                UserLeaveChannelEvent(this, new UserLeaveEventArgs(msg.Parameters[1]));
        }

        private void ircCore_PartEvent(object sender, HandyIRCMessage msg)
        {
            OnLeave(msg.Parameters[0], msg.Nick);

            if (UserLeaveChannelEvent != null)
                UserLeaveChannelEvent(this, new UserLeaveEventArgs(msg.Nick));
        }

        private void ircCore_JoinEvent(object sender, HandyIRCMessage msg)
        {
            // is me?
            if (msg.Nick == ircCore.UserNick)
            {
                channels.Add(new ChannelObject(this, ircCore, msg.Parameters[0]));

                if (JoinChannelEvent != null)
                    JoinChannelEvent(this, msg.Parameters[0]);
            }
            else if (UserJoinChannelEvent != null)
                UserJoinChannelEvent(this, new UserJoinEventArgs(msg.Nick, msg.Parameters[0]));
        }

        private void ircCore_RPL_NAMREPLYEvent(object sender, HandyIRCMessage msg)
        {
            string channel = msg.Parameters[2];
            string[] names = msg.Parameters[3].Split(' ');

            foreach (string s in names)
            {
                if (s != "")
                {
                    string name = "";
                    if (s[0] == '@' || s[0] == '+' || s[0] == '&' || s[0] == '%' || s[0] == '~')
                        name = s.Substring(1);
                    else
                        name = s;

                    if (UserJoinChannelEvent != null)
                        UserJoinChannelEvent(this, new UserJoinEventArgs(name, channel));
                }
            }
        }

        private void ircCore_NickEvent(object sender, HandyIRCMessage msg)
        {
            // is me?
            if (msg.Nick == options.NickName)
                options.NickName = msg.Nick;

            if (UserRenameEvent != null)
                UserRenameEvent(this, new UserRenameEventArgs(msg.Nick, msg.Parameters[0]));
        }

        private void ircCore_PrivmsgEvent(object sender, HandyIRCMessage msg)
        {
            switch (GetPrivmsgType(msg))
            {
                case PrivmsgType.channelAction:
                    {
                        break;
                    }
                case PrivmsgType.channelMessage:
                    {
                        if (ChannelMessageEvent != null)
                            ChannelMessageEvent(this, msg);

                        break;
                    }
                case PrivmsgType.ctcpRequest:
                    {
                        break;
                    }
                case PrivmsgType.queryAction:
                    {
                        break;
                    }
                case PrivmsgType.queryMessage:
                    {
                        if (GetPrivateObject(msg.Nick) == null)
                            CreatePrivateObject(msg.Nick, msg.Parameters[1]);
                        else
                        {
                            if (QueryMessageEvent != null)
                                QueryMessageEvent(this, msg);
                        }

                        break;
                    }
                case PrivmsgType.unknown:
                    {
                        throw new UnknownPrivmsgException();
                        break;
                    }
            }
        }

        private PrivmsgType GetPrivmsgType(HandyIRCMessage msg)
        {
            Regex actionRegex = new Regex("^:.*? PRIVMSG (.).* :" + "\x1" + "ACTION .*" + "\x1" + "$", RegexOptions.Compiled);
            Regex messageRegex = new Regex("^:.*? PRIVMSG (.).* :.*$", RegexOptions.Compiled);
            Regex ctcpRequestRegex = new Regex("^:.*? PRIVMSG .* :" + "\x1" + ".*" + "\x1" + "$", RegexOptions.Compiled);

            Match result = actionRegex.Match(msg.RawMessage);
            if (result.Success)
            {
                string s = result.Groups[1].Value;
                if (s == "#" || s == "!" || s == "@" || s == "+")
                    return PrivmsgType.channelAction;
                else
                    return PrivmsgType.channelMessage;
            }

            result = ctcpRequestRegex.Match(msg.RawMessage);
            if (result.Success)
            {
                return PrivmsgType.ctcpRequest;
            }

            result = messageRegex.Match(msg.RawMessage);
            if (result.Success)
            {
                string s = result.Groups[1].Value;
                if (s == "#" || s == "!" || s == "@" || s == "+")
                    return PrivmsgType.channelMessage;
                else
                    return PrivmsgType.queryMessage;
            }

            return PrivmsgType.unknown;
        }

        #endregion

        #region Properties

        public bool IsConnected
        {
            get
            {
                return ircCore.IsConnected;
            }
        }

        public ConsoleObject ConsoleObj
        {
            get
            {
                return consoleObj;
            }
        }

        public List<ChannelObject> Channels
        {
            get
            {
                return channels;
            }
        }

        public List<PrivateObject> Privates
        {
            get
            {
                return privates;
            }
        }

        public Options Options
        {
            get
            {
                return options;
            }
        }

        #endregion
    }
}
